﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FlatRedBall.Math;
using FlatRedBall.Math.Geometry;
using FlatRedBall;
using FlatRedBall.Input;
using Microsoft.Xna.Framework;
using FlatRedBall.AI.Pathfinding;
using Microsoft.Xna.Framework.Graphics;
using FlatRedBall.Instructions;



namespace TopDownShooter.Screens
{
    public class GameScreen : Screen
    {
        internal Player mainPlayer;
        Crosshair crosshair;

        public GameScreen()
            : base("GameScreen")
        {}
        public static int ObstacleCount = 40;
        public static int FieldWidth = 100;
        public static int FieldHeight = 100;
        public static float ObstacleSize = 6;
        public static float NodeSpacing = 1.5f;
        public static int EnemyCount = 5;


        internal PositionedObjectList<Enemy> enemies = new PositionedObjectList<Enemy>();
        internal PositionedObjectList<Pickup> pickups = new PositionedObjectList<Pickup>();
        public override void Initialize()
        {
            Hud = new Hud();
            mainPlayer = new Player(this);
            Hud.AttachTo(mainPlayer, false);
            crosshair = new Crosshair(this);
            CreateObstacleField();
            BuildNodeNetwork();
            network.LinkColor = new Color(1f, 1f, 1f, .7f);
            network.NodeColor = new Color(0f, 1f, 0f, .7f);
            //network.Visible = true;
            SpriteManager.Camera.Z = 100;
            
            EnemyCount = 2 * Game1.Level;
            for (int i = 0; i < EnemyCount; i++)
            {
                SpawnEnemy();
                SpawnAmmo();
                SpawnHealth();
            }
            // Set the screen up here instead of in the Constructor to avoid
            // exceptions occurring during the constructor.
            base.Initialize();
        }
        internal NodeNetwork network;
        
        private void BuildNodeNetwork()
        {
            network = new NodeNetwork();
            
            
            Circle collisionTestCircle = ShapeManager.AddCircle();
            collisionTestCircle.Radius = 1.2f;
            for (int i = 0; i < boxes.Count - 4; i++)
            {
                // Four corners of the box

                AddNode(
                    boxes[i].X + boxes[i].Rectangle.ScaleX * NodeSpacing,
                    boxes[i].Y + boxes[i].Rectangle.ScaleY * NodeSpacing,
                    collisionTestCircle);
                AddNode(
                    boxes[i].X - boxes[i].Rectangle.ScaleX * NodeSpacing,
                    boxes[i].Y + boxes[i].Rectangle.ScaleY * NodeSpacing,
                    collisionTestCircle);
                AddNode(
                    boxes[i].X + boxes[i].Rectangle.ScaleX * NodeSpacing,
                    boxes[i].Y - boxes[i].Rectangle.ScaleY * NodeSpacing,
                    collisionTestCircle);
                AddNode(
                    boxes[i].X - boxes[i].Rectangle.ScaleX * NodeSpacing,
                    boxes[i].Y - boxes[i].Rectangle.ScaleY * NodeSpacing,
                    collisionTestCircle);
                
            }
            Line directLineTester = ShapeManager.AddLine();
            for (int i = 0; i < network.Nodes.Count; i++)
            {
                List<PositionedNode> closestFour = getClosestNodesToNode(network.Nodes[i], 10);
                for (int j = 0; j < closestFour.Count; j++)
                {
                    directLineTester.SetFromAbsoluteEndpoints(network.Nodes[i].Position, closestFour[j].Position);
                    bool clear = true;
                    for (int k = 0; k < boxes.Count; k++)
                    {
                        if (boxes[k].Rectangle.CollideAgainst(directLineTester))
                        {
                            clear = false;
                            break;
                        }
                    }
                    if (clear)
                    {
                        network.Nodes[i].LinkTo(closestFour[j], 1);
                    }
                }
            }
            for (int i = 0; i < network.Nodes.Count; i++)
            {
                if (network.Nodes[i].X < -GameScreen.FieldWidth - 50 || network.Nodes[i].X > GameScreen.FieldWidth + 50 || network.Nodes[i].Y < -GameScreen.FieldHeight - 50 || network.Nodes[i].Y > GameScreen.FieldHeight + 50)
                {
                    network.Remove(network.Nodes[i]);
                    i--;
                }
            }
            ShapeManager.Remove(collisionTestCircle);
        }
        internal List<PositionedNode> getClosestNodesToNode(PositionedNode n, int amount)
        {
            return getClosestNodesToPoint(n.Position, amount);
        }
        internal List<PositionedNode> getClosestNodesToPoint(Vector3 n, int amount)
        {
            List<PositionedNode> sortedByDistance = new List<PositionedNode>(network.Nodes);
            double[] distances = new double[network.Nodes.Count];
            for (int i = 0; i < network.Nodes.Count; i++)
                distances[i] = Vector3.Distance(network.Nodes[i].Position, n);

            // Insertion sort
            for (int i = 1; i < network.Nodes.Count; i++)
            {
                double distance = distances[i];
                PositionedNode value = sortedByDistance[i];
                int j = i - 1;
                bool done = false;
                while (!done)
                {
                    if (distances[j] > distance)
                    {
                        sortedByDistance[j + 1] = sortedByDistance[j];
                        distances[j + 1] = distances[j];
                        j--;
                        if (j < 0)
                            done = true;
                    }
                    else
                        done = true;
                }
                sortedByDistance[j + 1] = value;
                distances[j + 1] = distance;
            }

            sortedByDistance.RemoveAt(0);
            while (sortedByDistance.Count > amount)
                sortedByDistance.RemoveAt(sortedByDistance.Count - 1);
            return sortedByDistance;
        }
        public void AddNode(float X, float Y, Circle collisionTestCircle)
        {
            PositionedNode node = network.AddNode();
            node.X = X;
            node.Y = Y;
            if (!NodeClear(node, collisionTestCircle))
                network.Remove(node);

        }
        public bool NodeClear(PositionedNode n, Circle test)
        {
            test.Position = n.Position;
            for(int i = 0; i < boxes.Count; i++)
            {
                if (boxes[i].Rectangle.CollideAgainst(test))
                    return false;
            }
            return true;
        }
        public void SpawnEnemy()
        {
            PositionedNode spawnNode = network.Nodes[Random.Next(network.Nodes.Count)];
            while (SpriteManager.Camera.IsPointInView(spawnNode.X, spawnNode.Y, 0))
            {
                spawnNode = network.Nodes[Random.Next(network.Nodes.Count)];
            }
            SpawnEnemy(spawnNode.X, spawnNode.Y);
        }
        public void SpawnEnemy(float X, float Y)
        {
            Enemy e = new Enemy(this);
            e.X = X;
            e.Y = Y;
            enemies.Add(e);
        }
        public void SpawnAmmo()
        {
            PositionedNode spawnNode = network.Nodes[Random.Next(network.Nodes.Count)];
            SpawnAmmo(spawnNode.X, spawnNode.Y);
        }
        public void SpawnAmmo(float X, float Y)
        {
            Ammo e = new Ammo();
            e.X = X;
            e.Y = Y;
            pickups.Add(e);
        }
        public void SpawnHealth()
        {
            PositionedNode spawnNode = network.Nodes[Random.Next(network.Nodes.Count)];
            SpawnHealth(spawnNode.X, spawnNode.Y);
        }
        public void SpawnHealth(float X, float Y)
        {
            Health e = new Health();
            e.X = X;
            e.Y = Y;
            pickups.Add(e);
        }
        private void CreateObstacleField()
        {
            Box box1 = addBox(0, 0);
            box1.Rectangle.ScaleX = FieldWidth + 15;
            box1.Y = -(FieldHeight + 15);
            boxes.Add(box1);

            Box box2 = addBox(0, 0);
            box2.Rectangle.ScaleY = FieldHeight + 15;
            box2.X = -(FieldWidth + 15);
            boxes.Add(box2);
            GenerateObstacles();
            MirrorObstacleField();
        }

        private void GenerateObstacles()
        {

            for (int i = 0; i < ObstacleCount + Random.Next(ObstacleCount / 2); i++)
            {
                bool not_clear = true;
                while (not_clear)
                    not_clear = !AddRandomBox();
            }
        }

        private void MirrorObstacleField()
        {
            int count = boxes.Count;
            for (int i = 0; i < count; i++)
            {
                Box box = addBox(0, 0);
                box.Rectangle.ScaleX = boxes[i].Rectangle.ScaleX;
                box.Rectangle.ScaleY = boxes[i].Rectangle.ScaleY;

                box.X = -boxes[i].Rectangle.X;
                box.Y = -boxes[i].Rectangle.Y;
                
                boxes.Add(box);
            }
        }

        private bool AddRandomBox()
        {
            double width = 1.5 + Random.NextDouble() * ObstacleSize;
            double height = 1.5 + Random.NextDouble() * ObstacleSize;
            Box box = addBox(0, 0);
            box.Rectangle.ScaleX = (float)width;
            box.Rectangle.ScaleY = (float)height;
            // Randomly place it on the right side of the field, ensuring that it can't touch when flipped
            box.X = Random.Next(FieldWidth) + 6;
            box.Y = Random.Next(FieldHeight * 2) - (FieldHeight);
            foreach (Box b in boxes)
            {
                if (b.Rectangle.CollideAgainst(box.Rectangle))
                {
                    box.Destroy();
                    return false;
                }
            }

            if (mainPlayer.Circle.CollideAgainst(box.Rectangle))
            {
                box.Destroy();
                return false;
            }
            boxes.Add(box);
            return true;
        }
        public void ClearScreen()
        {
            while(boxes.Count > 0)
                boxes[0].Destroy();
            while(allBullets.Count > 0)
                allBullets[0].Destroy();
            while (enemies.Count > 0)
                enemies[0].Destroy();
            while (pickups.Count > 0)
                pickups[0].Destroy();
            mainPlayer.Destroy();
            crosshair.Destroy();
            Hud.Destroy();
            while (ShapeManager.VisibleLines.Count > 0)
                ShapeManager.Remove(ShapeManager.VisibleLines[0]);
            while (ShapeManager.VisiblePolygons.Count > 0)
                ShapeManager.Remove(ShapeManager.VisiblePolygons[0]);
            
        }
        internal Box addBox(float X, float Y)
        {
            Box b = new Box();
            b.X = X;
            b.Y = Y;
            return b;
        }
        public void Pause()
        {
            try
            {
                InstructionManager.PauseEngine();
                LoadPopup(typeof(PauseScreen).FullName, false);
            }
            catch
            {
                InstructionManager.UnpauseEngine();
            }
        }
        public void Unpause()
        {
            InstructionManager.UnpauseEngine();
        }
        
        public static Random Random = new Random();
        internal PositionedObjectList<Bullet> allBullets = new PositionedObjectList<Bullet>();
        internal PositionedObjectList<Box> boxes = new PositionedObjectList<Box>();
        internal Hud Hud;
        public override void Activity(bool firstTimeCalled)
        {
            if (InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.OemTilde))
            {
                network.Visible = !network.Visible;
                network.UpdateShapes();
            }
            if (InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.Escape))
            {
                if(!InstructionManager.IsEnginePaused)
                    Pause();
                else
                    Unpause();
            }
            if (InstructionManager.IsEnginePaused)
                return;
            if (!FlatRedBallServices.Game.IsActive)
            {
                Pause();
            }
            mainPlayer.Activity();
            crosshair.Activity();
            for (int i = 0; i < enemies.Count; i++)
            {
                for (int j = 0; j < enemies.Count; j++)
                {
                    if (i == j) continue;
                    if (enemies[j].NoClip || enemies[i].NoClip) continue;
                    if ((enemies[j].Position - enemies[i].Position).Length() < 10 && enemies[j].Circle.CollideAgainstMove(enemies[i].Circle, 1, 1))
                    {
                        enemies[j].CollidedAgainstEnemy(enemies[i]);
                        enemies[i].CollidedAgainstEnemy(enemies[j]);
                    }
                }
                for (int j = 0; j < pickups.Count; j++)
                {
                    if ((pickups[j].Position - enemies[i].Position).Length() < 10 && pickups[j].Rectangle.CollideAgainst(enemies[i].Circle))
                    {
                        pickups[j].PickedUpBy(enemies[i]);
                    }
                }
                for (int j = 0; j < mainPlayer.bulletsFired.Count; j++)
                {
                    if (mainPlayer.bulletsFired[j].m_Line.CollideAgainst(enemies[i].Circle))
                    {
                        enemies[i].onBulletCollide(mainPlayer.bulletsFired[j]);
                        j--;
                    }
                }
                if (enemies[i].Circle.CollideAgainstMove(mainPlayer.Circle, 0, 1))
                {
                }

                enemies[i].Activity();
            }

            for (int j = 0; j < pickups.Count; j++)
            {
                if ((pickups[j].Position - mainPlayer.Position).Length() < 10 && pickups[j].Rectangle.CollideAgainst(mainPlayer.Circle))
                {
                    pickups[j].PickedUpBy(mainPlayer);
                }
            }
            mainPlayer.PointAt(crosshair.Position);

            for (int i = 0; i < allBullets.Count; i++)
            {
                allBullets[i].Activity();
                if (allBullets.Count > i && allBullets[i].Source != mainPlayer && allBullets[i].m_Line.CollideAgainst(mainPlayer.Circle))
                {
                    mainPlayer.onBulletCollide(allBullets[i]);
                }
            }
            for(int i = 0; i < boxes.Count; i++)
            {
                for (int j = 0; j < allBullets.Count; j++)
                {
                    if ((allBullets[j].Position - boxes[i].Position).Length() < 10 && allBullets[j].m_Line.CollideAgainst(boxes[i].Rectangle))
                    {
                        boxes[i].onBulletCollide(allBullets[j]);
                        j--;
                    }
                }
                for (int j = 0; j < enemies.Count; j++)
                {
                    if (enemies[j].Circle.CollideAgainstMove(boxes[i].Rectangle, 0, 1))
                    {
                        enemies[j].CollidedAgainstBox(boxes[i]);
                    }
                }
                mainPlayer.Circle.CollideAgainstMove(boxes[i].Rectangle, 0, 1);
            }
            if (enemies.Count == 0)
            {
                Game1.Level++;
                MoveToScreen(typeof(LevelUpScreen).FullName);
            }
            Hud.Activity();
            base.Activity(firstTimeCalled);
        }

        public override void Destroy()
        {
            ClearScreen();
            base.Destroy();
        }

        internal void playerDeath()
        {
            MoveToScreen(typeof(GameOverScreen).FullName);
        }
    }
}
